use askama::Template;
use axum::{
    body::Body,
    http::{self, header, HeaderValue, Response, StatusCode},
    response::{Html, IntoResponse},
};
use bytes::{BufMut, BytesMut};
use config::{init_config, AppContext};
use error::Error;
use once_cell::sync::Lazy;
use serde::{de::DeserializeOwned, Deserialize, Serialize};

#[macro_use]
extern crate rbatis;
pub mod auth;
pub mod config;
pub mod controller;
pub mod domain;
pub mod error;
pub mod job;
pub mod service;
pub mod template;
pub mod utils;

pub type Result<T> = std::result::Result<T, Error>;

pub const CODE_SUCCESS: &str = "200";
pub const CODE_FAIL: &str = "-1";
pub const STATIC_PREFIX: &str = "/assets";
pub const STATIC_PATH: &str = "assets";
pub const FILE_PATH: &str = "images";
pub const DB_FILE_NAME: &str = "blog.db";
pub const CONFIG_FILE_NAME: &str = "config.json";
pub const DATA_ROOT: &str = ".blog";
pub const TOKEN_NAME: &str = "tk";

pub static CONTEXT: Lazy<AppContext> = Lazy::new(|| {
    let mut app_context = AppContext::default();
    let (config, rb) = init_config();
    app_context.config = config;
    app_context.rb = rb;
    app_context
});

#[derive(Debug, Serialize, Clone)]
pub struct ApiResponse<T: Serialize> {
    pub result: Option<String>,
    pub message: Option<String>,
    pub data: Option<T>,
}

impl<T: Serialize> IntoResponse for ApiResponse<T> {
    fn into_response(self) -> http::Response<Body> {
        let mut buf = BytesMut::with_capacity(128).writer();
        match serde_json::to_writer(&mut buf, &self) {
            Ok(()) => (
                [(
                    header::CONTENT_TYPE,
                    HeaderValue::from_static(mime::APPLICATION_JSON.as_ref()),
                )],
                buf.into_inner().freeze(),
            )
                .into_response(),
            Err(err) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                [(
                    header::CONTENT_TYPE,
                    HeaderValue::from_static(mime::TEXT_PLAIN_UTF_8.as_ref()),
                )],
                err.to_string(),
            )
                .into_response(),
        }
    }
}
impl<T> ApiResponse<T>
where
    T: Serialize + DeserializeOwned + Clone,
{
    pub fn from_result(result: Result<T>) -> Self {
        match result {
            Ok(data) => Self {
                result: Some(CODE_SUCCESS.to_string()),
                message: None,
                data: Some(data),
            },
            Err(e) => Self::from_error(e.to_string().as_str()),
        }
    }
    pub fn from_option(option: Option<T>) -> Self {
        match option {
            Some(data) => Self {
                result: Some(CODE_SUCCESS.to_string()),
                message: None,
                data: Some(data),
            },
            None => Self::from_error("none"),
        }
    }
    pub fn from(data: T) -> Self {
        Self {
            result: Some(CODE_SUCCESS.to_string()),
            message: None,
            data: Some(data),
        }
    }

    pub fn from_error(error: &str) -> Self {
        Self {
            result: Some(CODE_FAIL.into()),
            message: Some(error.to_owned()),
            data: None,
        }
    }
}

impl<T> ToString for ApiResponse<T>
where
    T: Serialize + DeserializeOwned + Clone,
{
    fn to_string(&self) -> String {
        serde_json::to_string(self).unwrap()
    }
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
pub struct QueryParam {
    pub page_no: Option<u64>,
    pub page_size: Option<u64>,
    //搜索关键字
    pub kw: Option<String>,
    //分类
    pub c: Option<u32>,
    //标签
    pub t: Option<String>,

    pub id: Option<u64>,
}
#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
pub struct CommParam {
    pub id: Option<u64>,
    pub rid: Option<u64>,
    pub pwd: Option<String>,
    pub redirect: Option<String>,
    pub thum: Option<bool>,
}
pub struct TemplateResponse<T>(T);
impl<T> IntoResponse for TemplateResponse<T>
where
    T: Template,
{
    fn into_response(self) -> Response<Body> {
        match self.0.render() {
            Ok(html) => Html(html).into_response(),
            Err(err) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Failed to render template. : {err}"),
            )
                .into_response(),
        }
    }
}
pub struct XmlTemplateResponse<T>(T);
impl<T> IntoResponse for XmlTemplateResponse<T>
where
    T: Template,
{
    fn into_response(self) -> Response<Body> {
        match self.0.render() {
            Ok(xml) => (
                [(
                    header::CONTENT_TYPE,
                    HeaderValue::from_static(mime::TEXT_XML.as_ref()),
                )],
                xml,
            )
                .into_response(),
            Err(err) => (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("Failed to render template. : {err}"),
            )
                .into_response(),
        }
    }
}
